package org.eoti.math.cf;

import org.eoti.math.Fraction;

import java.math.BigInteger;
import java.util.Iterator;

/**
 * The MIT License
 * <p/>
 * Copyright (c) 2009 Malachi de AElfweald (malachid@gmail.com)
 * <p/>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * <p/>
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 * <p/>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
public class ConvergentIterator
implements Iterator<Fraction>
{
	private final static BigInteger three = BigInteger.valueOf(3);
	private final static BigInteger two = BigInteger.valueOf(2);
	private final static BigInteger one = BigInteger.ONE;
	private final static BigInteger zero = BigInteger.ZERO;

	private ContinuedFraction cf;
	private Fraction[] computed = new Fraction[3];
	private Fraction[] iterated = new Fraction[3];
	private Iterator<Fraction> iterator;
	private boolean firstPass = true;
	private boolean done = false;
	private int index = 0;

	public ConvergentIterator(ContinuedFraction cf)
	{
		this.cf = cf;
		for(int i=0; i<3; i++)
			computed[i] = new Fraction(null, null);

		iterator = cf.getValues().iterator();
	}

	/**
	 * Returns {@code true} if the iteration has more elements. (In other words, returns {@code true} if {@link #next}
	 * would return an element rather than throwing an exception.)
	 *
	 * @return {@code true} if the iteration has more elements
	 */
	public boolean hasNext(){
		if(done) return false;
		return iterator.hasNext();
	}

	/**
	 * Returns the next element in the iteration.
	 *
	 * @return the next element in the iteration
	 *
	 * @throws java.util.NoSuchElementException
	 *          if the iteration has no more elements
	 */
	public Fraction next(){
		if(index > 6) index = index%3+3;
		
		int current = index%3;
		int previous = (index-1)%3;
		int old = (index-2)%3;

		iterated[current] = iterator.next();
		if(iterated[current] == null) return null;

		if(firstPass)
		{
			if(index == 0)
			{
				if(iterated[current].getNumerator() == null)
				{
					done = true;
					if(iterated[current].getDenominator() == null) return null;
					// Integer
					return new Fraction(iterated[current].getDenominator(), one);
				}
				if(iterated[current].getDenominator() == null)
				{
					done = true;
					// Integer
					return new Fraction(iterated[current].getNumerator(), one);
				}
				computed[current].setNumerator( iterated[current].getDenominator() );
				computed[current].setDenominator( one );
			}else{
				computed[current].setNumerator(
						(
								iterated[current].getDenominator().multiply(
										computed[previous].getNumerator()
								)
						).add( iterated[previous].getNumerator() ));
				computed[current].setDenominator( iterated[current].getDenominator() );
				firstPass = false;
			}
		}else{
			computed[current].setNumerator(
					(
							iterated[current].getDenominator().multiply(
								computed[previous].getNumerator()
							)
					).add(
							iterated[previous].getNumerator().multiply(
									computed[old].getNumerator()
							)
					)
			);
			computed[current].setDenominator(
					(
							iterated[current].getDenominator().multiply(
									computed[previous].getDenominator()
							)
					).add(
							iterated[previous].getNumerator().multiply(
									computed[old].getDenominator()
							)
					)
			);
		}

		index++;
		return computed[current];
	}

	/**
	 * Removes from the underlying collection the last element returned by this iterator (optional operation).  This method
	 * can be called only once per call to {@link #next}.  The behavior of an iterator is unspecified if the underlying
	 * collection is modified while the iteration is in progress in any way other than by calling this method.
	 *
	 * @throws UnsupportedOperationException if the {@code remove} operation is not supported by this iterator
	 * @throws IllegalStateException         if the {@code next} method has not yet been called, or the {@code remove}
	 *                                       method has already been called after the last call to the {@code next} method
	 */
	public void remove(){
		throw new UnsupportedOperationException("remove is not supported");
	}
}
